﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace NATP2P.UDPNat
{

    class HolePunchPeer
    {
        
        public event EventHandler<StateChangedEventArgs> RaiseStateChangedEvent;
        IPEndPoint _LocalEndPoint;
        IPEndPoint _RemoteEndPoint;
        Socket _datagram = null;
        TypeNat _typenat;
        byte[] _receivePacket = new byte[4];
        NATHeader natHeader = new NATHeader();
        private EndPoint remoteEP = (EndPoint)(new IPEndPoint(IPAddress.Any, 0));
        
        public bool Connected { get; set; }
        public HolePunchPeer(IPEndPoint LocalEndPoint, IPEndPoint RemoteEndPoint, TypeNat typenat)
        {

            if ((LocalEndPoint == null) || (RemoteEndPoint == null))
            {
                throw new ArgumentNullException();
            }
            this._LocalEndPoint = LocalEndPoint;
            this._RemoteEndPoint = RemoteEndPoint;
            this._typenat = typenat;
            this.Connected = false;
            _datagram = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            _datagram.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            _datagram.Bind(_LocalEndPoint);
            _datagram.BeginReceiveFrom(_receivePacket, 0, _receivePacket.Length, SocketFlags.None, ref remoteEP, new AsyncCallback(End_Receive), _datagram);

        }

        private void End_Receive(IAsyncResult so)
        {
                int num = _datagram.EndReceiveFrom(so, ref remoteEP);
                 ProcessPacket(_receivePacket);
                
        }
        
        private void ProcessPacket(byte[] _receivePacket)
        {
           

           
            natHeader.Parse(_receivePacket);
            if (natHeader.MessageType == (int)MesgType.BindingAccpt)
            {
                    this.Connected = true;
                    IPEndPoint REndPoint = new IPEndPoint(_RemoteEndPoint.Address, natHeader.Port);
                    this.Close();
                    OnRaiseStateChangedEvent(new StateChangedEventArgs(REndPoint));
 
            }
            else
            {
                if (natHeader.MessageType == (int)MesgType.BindingConnect)
                {
                    this.Connected = true;
                    //_RemoteEndPoint.Port = natHeader.Port;
                    byte[] header = natHeader.ToByte((int)MesgType.BindingAccpt, false, natHeader.NPacket, natHeader.Port);
                    for (int i = 0; i < 10; i++)
                   _datagram.BeginSendTo(header, 0, header.Length, SocketFlags.None, remoteEP as IPEndPoint, new AsyncCallback(EndSend), _datagram);
                  //  IPEndPoint REndPoint = new IPEndPoint(_RemoteEndPoint.Address, natHeader.Port);
                    this.Close();
                    OnRaiseStateChangedEvent(new StateChangedEventArgs(remoteEP as IPEndPoint));
                    //_datagram.BeginReceiveFrom(_receivePacket, 0, _receivePacket.Length, SocketFlags.None, ref remoteEP, new AsyncCallback(End_Receive), _receivePacket); 
                }

                else {
                    _datagram.BeginReceiveFrom(_receivePacket, 0, _receivePacket.Length, SocketFlags.None, ref remoteEP, new AsyncCallback(End_Receive), _receivePacket); 
                }
            }
           

        }
        Thread handel1;
        public void KeepHoleAlive(object o)
        {
            handel1 = Thread.CurrentThread;
            while (!this.Connected)
            {
                    
                    byte[] header = natHeader.ToByte((int)MesgType.BindingListen, false, 0, _RemoteEndPoint.Port);
                    _datagram.BeginSendTo(header, 0, header.Length, SocketFlags.None, _RemoteEndPoint, new AsyncCallback(EndSend), _datagram);
                 
                Thread.Sleep(10000);
            }
        }
        Thread handel2;
        public void PunchHole(object o)
        {
            handel2 = Thread.CurrentThread;
            //the hedaerthat can be send to ather peer to make connection
            /*
              0  1  2  3  4  5  6  7  0  1  2  3  4  5  6  7
             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             |  Message Type   | EH |     packet number    |
             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
             |              Destination Port               | 
             +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
              
             
             */

            byte[] header;

            if (_typenat == TypeNat.Symmetric)
            {
                for (int p = 0; p < 255; p++)
                {
                    for (int i = 0; i < 128; i++)
                    {
                        header = natHeader.ToByte((int)MesgType.BindingConnect, false, 0, _RemoteEndPoint.Port);
                        _datagram.BeginSendTo(header, 0, header.Length, SocketFlags.None, _RemoteEndPoint, new AsyncCallback(EndSend), _datagram);

                    }
                    _RemoteEndPoint.Port++;
                     
                    Console.WriteLine(_RemoteEndPoint.Port);
                }
            }
            else
            {

                while (!this.Connected)
                {
                    try
                    {
                        header = natHeader.ToByte((int)MesgType.BindingConnect, false, 0, _RemoteEndPoint.Port);
                        _datagram.BeginSendTo(header, 0, header.Length, SocketFlags.None, _RemoteEndPoint, new AsyncCallback(EndSend), _datagram);
                    }
                    catch { }
                    Thread.Sleep(10000);
                    
                }
                   
                    

            }

        }

        public void Close()
        {
           if(handel1!=null)
            if (handel1.IsAlive)
                handel1.Abort();
           if (handel2 != null)
            if (handel2.IsAlive)
                handel2.Abort();
            _datagram.Dispose();
        }
        private void EndSend(IAsyncResult so)
        {
            if (_datagram!=null)
            {
                _datagram.EndSendTo(so);
                
            }
            
        }
        private void OnRaiseStateChangedEvent(StateChangedEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<StateChangedEventArgs> handler = RaiseStateChangedEvent;

            // Event will be null if there are no subscribers
            if (handler != null)
            {
                // Use the () operator to raise the event.
                handler(this, e);
            }
        }
    }
}
